package org.abettor.angleworm.auth;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.abettor.util.Environment;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.ControllerThreadSocketFactory;
import org.apache.commons.httpclient.protocol.ProtocolSocketFactory;

/**
 * HTTPS链接工厂，以便用户检查证书并决定是否信任之
 * @author shawn
 *
 */
public class HttpsSocketFactory implements ProtocolSocketFactory
{
	private String cert;
	private String passwd;

	/**
	 * 构造函数
	 * @param cert 证书名称
	 * @param passwd 证书存储密码
	 */
	public HttpsSocketFactory(String cert, String passwd)
	{
		this.cert = cert;
		this.passwd = passwd;
	}

	//	获取安全连接工厂类
	private SSLSocketFactory getSSLSocketFactory() throws IOException
	{
		TrustManagerFactory tmf = null;
		try
		{
			//	构建证书存储信息对象
			String filePath = Environment.getFilePath("conf/" + cert + ".public.jks");
			InputStream fis = new FileInputStream(new File(filePath));
			KeyStore ks = KeyStore.getInstance("JKS");
			ks.load(fis, passwd.toCharArray());

			//	构建证书管理器工厂对象
			tmf = TrustManagerFactory.getInstance("SunX509");
			tmf.init(ks);
		}
		catch (KeyStoreException e)
		{
			throw new IOException("Cannot open specified keystore", e);
		}
		catch (NoSuchAlgorithmException e)
		{
			throw new IOException("No algorithm named \"SunX509\" found", e);
		}
		catch (CertificateException e)
		{
			throw new IOException("Certificate info is not trusted", e);
		}

		SSLContext ctx = null;
		{
			try {ctx = SSLContext.getInstance("TLS");}catch (NoSuchAlgorithmException e){};
			if(ctx == null)
				try {ctx = SSLContext.getInstance("SSLv2");}catch (NoSuchAlgorithmException e){};
			if(ctx == null)
				try {ctx = SSLContext.getInstance("SSL");}catch (NoSuchAlgorithmException e){};
			if(ctx == null)
				return null;
		}
		try
		{
			ctx.init(null, tmf.getTrustManagers(), null);
		}
		catch (KeyManagementException e)
		{
			throw new IOException("Could not access the KeyManagement", e);
		}

		return ctx.getSocketFactory();
	}

	//	获取IPv4地址，防止DNS默认得到IPv6地址且客户网络不支持而造成NoRouteToHostException异常
	private synchronized String getInet4Address(String host) throws UnknownHostException
	{
		String key1 = "java.net.preferIPv4Stack";
		String key2 = "java.net.preferIPv6Addresses";
		String old1 = System.getProperty(key1, "false");
		String old2 = System.getProperty(key2, "false");

		System.setProperty(key1, "true");
		System.setProperty(key2, "false");
		InetAddress ia = InetAddress.getByName(host);
		String addr = ia.getHostAddress();

		System.setProperty(key1, old1);
		System.setProperty(key2, old2);
		return addr;
	}

	@Override
	public Socket createSocket(String host, int port) throws IOException, UnknownHostException
	{
		host = getInet4Address(host);
		SSLSocketFactory sf = getSSLSocketFactory();
		return sf.createSocket(host, port);
	}

	@Override
	public Socket createSocket(String host, int port, InetAddress localAddr, int localPort)
	throws IOException, UnknownHostException
	{
		host = getInet4Address(host);
		SSLSocketFactory sf = getSSLSocketFactory();
		return sf.createSocket(host, port, localAddr, localPort);
	}

	@Override
	public Socket createSocket(String host, int port, InetAddress localAddr, int localPort, HttpConnectionParams params)
	throws IOException, UnknownHostException, ConnectTimeoutException
	{
		host = getInet4Address(host);
		SSLSocketFactory sf = getSSLSocketFactory();
		int timeout = params.getConnectionTimeout();
		if(timeout == 0)
		{
			return sf.createSocket(host, port, localAddr, localPort);
		}

		return ControllerThreadSocketFactory.createSocket(this, host, port, localAddr, localPort, timeout);
	}
}
